
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;                            CPUTYPE 
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;    Display information about CPU.  Demonstrates use of a 
;    number of individual detection subroutines.  Command line
;    options include:
;       -       Prevent test which require the use of protected
;               mode instructions, which might not be handled
;               correctly V86 mode.  
;       +       Show raw timing values for cache and CPU speed.
;
;    Due to a bug in Windows, only the CPU ID tests can be run.
;    If Windows is running, all other tests are supressed.
;    The problem occurs in the execution of the RDMSR instruction
;    on a non-IBM chip (which is not supported on other chips).
;    Even though the bad opcode interrupt is properly intercepted
;    by this program, Windows kills the program anyway!
; 
;    (c) Copyright 1994  Frank van Gilluwe  All Rights Reserved. 

include undocpc.inc

cseg    segment para public
        assume  cs:cseg, ds:cseg, ss:stacka


;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ DATA AREA ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

; ÄÄÄÄÄ general data

        db      'CPUTYPE v1.01 '
        db      '(c) 1994 Frank van Gilluwe',0

cpuhead db      CR, LF, CR, LF
        db      '  CPU ANALYSIS AND INFORMATION'
        db      '                           v1.01 (c) 1994 FVG'
        db      CR, LF
        db      '  ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      CR, LF, '$'

cpu_val  db     0                  ; CPU value from CPUVALUE
                                   ;   0 = 8088/8086 or V20/V30
                                   ;   1 = 80186/80188
                                   ;   2 = 80286
                                   ;   3 = 80386
                                   ;   4 = 80486
                                   ;   5 = Pentium

cpu_info db     0                  ; flags from CPUVALUE
                                   ;   bit 0 = 1 if CPUID ok 
                                   ;       1 = 1 if V20/V30 CPU

cpu_mfg  db     0                  ; CPU manufacturer
                                   ;   0 = unknown
                                   ;   1 = string from CPUID
                                   ;   2 = NEC
                                   ;   4 = likely Intel or AMD
                                   ;   3 = Intel
                                   ;   5 = AMD
                                   ;   6 = IBM
                                   ;   7 = Cyrix or TI

cpuid_txt db    '               '

cpu_prot db     0                  ; protected state
                                   ;   0 = no protected mode 
                                   ;   1 = real mode
                                   ;   2 = protected mode
                                   ;   3 = V86 mode

cpu_priv db     0                  ; privledge level 0-3

fpu_val  db     0                  ; FPU value from FPUTYPE
                                   ;   0 = none
                                   ;   1 = 8087
                                   ;   2 = 80287
                                   ;   3 = 80387
                                   ;   4 = 80486DX or 
                                   ;         80486SX with 80387
                                   ;   5 = Pentium

fpu_info db     0                  ; FPU flags (when present)
                                   ;   bit 0 = 1 if FPU is in CPU

cmd_line db     0                  ; "-" = skip problem tests
                                   ; "+" = show cache timings
                                   ; "c" = cpu info only
                                   ; "u" = undoc info only


old_int6_seg dw 0                  ; temp storage for old int 6
old_int6_off dw 0                  ;  vector (bad opcode)
old_intD_seg dw 0                  ; temp storage for old int D
old_intD_off dw 0                  ;  vector (protection fault)

badoff       dw 0                  ; temp return offset if bad offset
                                   ;  interrupt 6 called

int_mask     db 0                  ; save area for interrupt mask


; ÄÄÄÄÄ strings for CPU type

cpumsg  db      CR, LF
        db      '  CPU type:              '
cpun    db                             'NEC V20/V30'
fpun    db      '                      ', CR, LF, '$'

cpuname db      '8088/8086  '
cpunend db      '80186/80188'
        db      '80286      '
        db      '80386      '
        db      '80486SX    '
        db      'Pentium    '

fpuname db      ' without FPU'
fpunend db      ' with 8087  '
        db      ' with 80287 '
        db      ' with 80387 '

fpuin2  db      ' with FPU        ' 
fpuin   db      ' (FPU inside CPU)'
fpuine  label   byte

; ÄÄÄÄÄ strings for CPU vendor

cpuvend db      '  CPU vendor:            $'

vendors db      'Unknown            $'  ; 8088 to 80286
vendend db      'From CPUID         $'  ; vendor in CPUID string
        db      'NEC                $'  ; V20/V30
        db      'Intel or AMD       $'  ; Not IBM or Cyrix
        db      'Intel              $'  ; Pentium CPUID=Intel
        db      'AMD                $'  ; only one to make 40Mhz 386
        db      'IBM                $'  ; only 386/486 with RDMSR
        db      'Cyrix or TI        $'  ; no UMOV support

crlf    db      CR, LF, '$'

idstring db     '123456780123$'     ; 12 byte CPUID string

cpuvval db      0



; ÄÄÄÄÄ strings for CPU speed

cpuspd  db      '  Internal CPU speed:    '
cpusval db      '   Mhz                 ', CR, LF, '$'

; Data Speed table - The first word is the number of times 
;   to repeat the loop.  Larger numbers take longer, and are 
;   used to compensate for faster CPUs.  The second word is a 
;   factor adjustment to end up with the speed in Mhz.  For 
;   example, on a 80486 at 33Mhz, the 2nd word is 16550 = (33 
;   * 2006 ticks)/4.  Three tables are used, since instruction 
;   timing is different on these CPUs 

intelspd dw      1, 10848       ; 8088  - loop duration, factor adjust
         dw      1, 10848       ; 80186  (5Mhz =  ~8345 ticks)
         dw      2, 3234        ; 80286  (12Mhz = ~1035 ticks)
         dw     10, 16200       ; 80386  (33Mhz = ~1917 ticks)
         dw     10, 16550       ; 80486  (33Mhz = ~2006 ticks)
         dw     20, 34318       ; Pentium (60Mhz = ~2269 ticks)

cyrixspd dw      1, 10848       ; not used
         dw      1, 10848       ; not used
         dw      2, 3234        ; not used
         dw     10, 16200       ; not used
         dw     10, 16550       ; 80486  (33Mhz = ~2006 ticks)
         dw     20, 34318       ; not used

ibmspd   dw      1, 10848       ; not used
         dw      1, 10848       ; not used
         dw      2, 3234        ; not used
         dw     10, 16200       ; 80386  (33Mhz = ~1917 ticks)
         dw     10, 16550       ; 80486  (33Mhz = ~2006 ticks)
         dw     20, 34318       ; not used


; ÄÄÄÄÄ strings for pre-fetch queue

cpupf   db      '  Prefetch queue size:   '
cpupfq  db      '          ', CR, LF, '$'


; ÄÄÄÄÄ strings for BIOS routine - step saved after reset

bver    db      '  CPU version from BIOS: '
bvernum db                             '    h  '
bvercom db      '                '
        db      CR, LF, '$'

novermg db      '  CPU version detection: '
        db      'Not supported in this BIOS or CPU'
        db      CR, LF, '$'

idtxtmg db      '  CPUID - Vendor string: '
idtext  db      '             ', CR, LF, '$'
stepmsg db      '    Stepping ID:          '
modval  db      '   ', CR, LF, '$'

stepa   db      '  CPU version detection: '
        db      'Step A (earliest released version)'
        db      CR, LF, '$'

; table of step values and what they mean

bvt     dw      303h
        db      '(80386 step B1)'
bvte    dw      305h
        db      '(80386 step D0)'
        dw      308h
        db      '(80386 step D1)'
        dw      400h
        db      '(80486 step A0)'
        dw      401h
        db      '(80486 step B2)'
        dw      403h
        db      '(80486 step B3)'
        dw      404h
        db      '(80486 step B4)'
        dw      405h
        db      '(80486 step B5)'
        dw      406h
        db      '(80486 step B6)'
        dw      407h
        db      '(80486 step C1)'
        dw      2300h
        db      '(80386SX)      '
        dw      0A301h
        db      '(386SLC step 1)'       ; IBM
        dw      0A412h
        db      '(486SLC)       '       ; IBM
        dw      0A422h
        db      '(486SLC2)      '       ; IBM
        dw      0FFFFh

; ÄÄÄÄÄ text for CPU mode (286 or greater only)

mode_V  db      '  Current CPU mode:      '
        db      'V86 (Protected, Virtual 8086)', CR, LF, '$'
mode_R  db      '  Current CPU mode:      '
        db      'Real', CR, LF, '$'
mode_P  db      '  Current CPU mode:      '
        db      'Protected, Privilege level '
mode_Pa db      '  ', CR, LF, '$'

; ÄÄÄÄÄ text for internal 

cache_D db      '  Internal CPU cache:    Disabled'
        db      CR, LF, '$'
cache_N db      '  Internal CPU cache:    '
        db      'Enabled, no write-through', CR, LF, '$'
cache_W db      '  Internal CPU cache:    '
        db      'Enabled, with write-through', CR, LF, '$'
cache_X db      '  Internal CPU cache:    None'
        db      CR, LF, '$'

cSizeAll    db  '    Internal cache size: $'
cSizeData   db  '    Data cache size:     $'
cSizeCode   db  '    Code cache size:     Assumed 8 KB (not measured)'
            db  CR, LF, '$'

cSize1      db                            '     ', CR, LF, '$'
cSize2      db                            '     ', CR, LF, '$'

cSizeNone   db  'External cache masks internal CPU cache'
            db  CR, LF, '$'

cache_time  db  '    Timings (.5 to 32K): '
cache_time1 db  '                                                     '
            db  CR, LF, '$'

; storage for test durations, data cache tests

timings     dw  0               ; .5K timing 
            dw  0               ; 1K 
            dw  0               ; 2K
            dw  0               ; 4K
            dw  0               ; 8K
            dw  0               ; 16K
            dw  0               ; 32K

windows     db  CR, LF
            db  '  Windows prevents other tests from running.'
            db  CR, LF, '$'


;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ CODE START ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

cputype proc    far

start:  
        xor     bl, bl
        cmp     byte ptr ds:[80h], 1 ; any characters on line ?
        jbe     no_options         ; jump if not
        mov     bl, ds:[82h]       ; get option on cmd line
no_options:
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        mov     [cmd_line], bl     ; save option
        OUTMSG  cpuhead            ; output header message

; get cpu type and cpumode, and save results

        call    cpuvalue           ; get CPU type 0 to 5 in al
        mov     [cpu_val], al      ; save
        mov     [cpu_info], ah     ; save flags
        call    cpumode            ; get current mode: V86, real,
                                   ;   protected into ax
        mov     [cpu_prot], al     ; save type of protection
        mov     [cpu_priv], ah     ; save privledge level

; --- determine CPU & FPU type and output message text

begin_cpu:
        mov     al, [cpu_val]      ; get previous saved cpu value
        mov     ah, [cpu_info]     ; get previous saved flags
        mov     di, offset fpun
        test    ah, 2              ; V20/V30 ?
        jnz     cpui_getfpu        ; jump if so
        mov     cx, offset cpunend - offset cpuname
        mul     cl                 ; ax=al*cl
        mov     si, offset cpuname
        add     si, ax
        mov     di, offset cpun
        cld
cpui_loop1:
        movsb                      ; xfer CPU name
        cmp     byte ptr [si], ' ' ; end of CPU name ?
        je      cpui_getfpu        ; exit if so
        loop    cpui_loop1

cpui_getfpu:
        call    fputype            ; detect FPU, 0 to 5 in al
        mov     [fpu_val], al      ; save

        cmp     [cmd_line], "-"    ; option to prevent test ?
        je      cpui_skp0          ; jump to skip test
        call    fpuloc             ; see if FPU inside CPU
        mov     [fpu_info], al     ; save 

cpui_skp0:
        mov     al, [fpu_val]
        cmp     al, 4              ; Pentium with FPU ?
        ja      cpui_skp3          ; jump if so
        jb      cpui_skp1          ; jump if not 80486 with math


        cmp     [cmd_line], "-"    ; option to prevent test ?
        jne     cpui_skp0a         ; jump if fpu_info valid
        mov     word ptr [di-2], '  '  ; blank out "SX"
        sub     di, 2
        mov     si, offset fpuin2
        jmp     cpui_skp4          ; just indicate system has FPU

cpui_skp0a:
        test    [fpu_info], 1      ; inside chip ?
        jnz     cpui_skp2
        mov     al, 3              ; must be 80486SX with 80387
cpui_skp1:
        mov     cx, offset fpunend - offset fpuname
        mul     cl                 ; ax = al * cl
        add     ax, offset fpuname
        mov     si, ax
        cld
        rep     movsb              ; xfer FPU name
        jmp     cpui_skp5

cpui_skp2:
        mov     byte ptr [di-2], 'D' ; it's a 80486DX
cpui_skp3:
        mov     si, offset fpuin
cpui_skp4:
        mov     cx, offset fpuine - offset fpuin
        cld
        rep     movsb              ; xfer "FPU inside" msg

cpui_skp5:
        OUTMSG  cpumsg             ; display result

; Now check if Windows is running, and exit if so.  See notes
;  at top about Windows problem in attempting to find the CPU
;  vendor, which is required for other tests.

        mov     ax, 1600h
        int     2Fh                ; Windows check
        cmp     al, 0              
        je      cpui_find_vendor   ; not running enhanced
        cmp     al, 80h
        je      cpui_find_vendor   ; not running enhanced
        OUTMSG  windows            ; don't run other tests
        jmp     cpui_exit

; --- determine CPU Vendor

cpui_find_vendor:
        call    cpuvendor          ; get vendor number in al
        mov     [cpu_mfg], al      ; save for later 

; --- determine CPU Speed

        call    cpuspeed           ; determine internal CPU speed

        cmp     [cmd_line], "+"    ; show raw timings ?
        jne     cpui_skp6
        push    ax
        mov     ax, bx
        mov     di, offset cpusval+10
        xor     bl, bl
        call    decw               ; insert raw timing value
        pop     ax
cpui_skp6:
        mov     di, offset cpusval
        xor     bl, bl
        call    decw               ; convert to decimal

; --- now adjust [cpu_mfg] if 386 at 40 Mhz (AMD)
;                          if 386 under 25 MHz (Intel)

        cmp     [cpu_val], 3       ; 386 ?
        jne     cpui_skp8          ; jump if not
        cmp     [cpu_mfg], 3       ; Now set to Intel or AMD?
        jne     cpui_skp8          ; jump if not
        mov     bl, 4              ; assume Intel
        cmp     ax, 21             ; 16 or 20 Mhz ?
        jbe     cpui_skp7          ; jump if so (very likely Intel)
        cmp     ax, 39             ; below 39 Mhz ?
        jb      cpui_skp8          ; if so, Intel or AMD
        cmp     ax, 41             ; above 41 Mhz ?
        ja      cpui_skp8          ; unclear what it is
        mov     bl, 5
cpui_skp7:
        mov     [cpu_mfg], bl      ; set to AMD or Intel, not both

; output vendors and speed strings

cpui_skp8:
        OUTMSG  cpuvend
        mov     al, [cpu_mfg]
        cmp     al, 1              ; from CPUID string ?
        jne     cpui_skp9          ; jump if not
        mov     cx, 12
        mov     dx, offset idstring
        jmp     cpui_skp10

cpui_skp9:
        mov     cx, offset vendend - offset vendors
        mul     cl                 ; ax = index into text strings
        add     ax, offset vendors
        mov     dx, ax
cpui_skp10:
        mov     ah, 9
        int     21h                ; output vendor name
        OUTMSG  crlf               ; linefeed

        OUTMSG  cpuspd             ; display processor speed


; --- find pre-fetch q size

        cmp     [cpu_val], 5       ; Pentium ?
        je      cpui_get_step      ; skip pre-fetch queue test if so
        call    cpuq               ; find cpu queue size (ax)
        call    outprefetch        ; display result


; --- try for CPU version id (for 386 and above)

cpui_get_step:
        call    cpustep            ; check for CPU step & output


; --- display CPU mode (for 286 and above)

        mov     al, [cpu_prot]     ; get protected/real status
        mov     ah, [cpu_priv]     ; get privilege level
        call    cpum_display       ; display results


; --- check internal cache information

        cmp     [cmd_line], "-"    ; prevent cache & undoc tests?
        jne     cpui_skp10a
        jmp     cpui_exit          ; jump if so

cpui_skp10a:
        call    cpu_cache          ; find internal CPU cache type
        call    cpu_cache_display  ; display results
        cmp     al, 2              ; cache present and enabled ?
        jb      cpui_exit          ; no cache or cache disabled

; --- find data cache size by timing reads for blocks of memory

        cmp     [cpu_val], 5       ; Pentium ?
        jb      cpui_skp11
        OUTMSG  cSizeData          ; display "data cache size"
        jmp     cpui_skp12

cpui_skp11:
        OUTMSG  cSizeAll           ; display "internal cache size"
cpui_skp12:
        call    cache_d_size       ; get size of data cache in ax
        cmp     ax, 0              ; size zero ?
        je      cpui_skp13
        cmp     ax, 32             ; 32K or more ?
        jae     cpui_skp13
        mov     di, offset cSize1
        xor     bl, bl
        call    decw
        inc     di
        mov     word ptr [di], 'BK' ; insert KB
        OUTMSG  cSize1     
        jmp     cpui_skp14

cpui_skp13:
        OUTMSG  cSizeNone          ; "external cache masks size"
cpui_skp14:
        call    timings_display    ; optional timing values display

; instruction cache on pentium message (not measured)

        cmp     [cpu_val], 5       ; Pentium ?
        jb      cpui_exit
        OUTMSG  cSizeCode

; All done

cpui_exit:
        mov     ah,4Ch
        int     21h                ; exit with al return code
cputype endp



;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
; all of the subroutines are located in CPUTYPE2

include cputype2.asm


cseg    ends


;===============================================================
; 2nd segment used for a large 32K subroutine

farseg  segment para public
        assume  cs:farseg, ss:stacka

;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    FLUSH QUEUE       
;       Execute 32K of NOPs to load up instruction cache with
;       NOPs.
;
;       Call with:      nothing 
;
;       Returns:        nothing
;
;       Regs used:      none

flush_q proc    far
        db      8000h dup (90h)   ; 32K of nops
        ret
flush_q endp

farseg  ends


;================================================== stack ======

stacka  segment para stack

        db      192 dup (0)

stacka  ends

        end     start



